#include "LineItem.h"
#include "CalculateFeaturePoints.h"
#include "CalculateIsPointNearLine2D.h"
#include "CalculateArrowPath.h"
#include "GraphicsScene.h"

#include "GraphicsItem.h"
#include "PortItem.h"
#include "NameItem.h"
#include "ArrowItem.h"
#include "NotifyItem.h"
#include "DataCenter/LineData.h"

#include <QPainter>
#include <QPainterPath>
#include <cmath>
#include "CalculateNotifyPoint.h"

namespace Graphics
{
	class LineItemPrivate
	{
	public:
		LineItemPrivate(LineItem * line)
			: _line(line)
		{
			_arrowPart = new ArrowItem(_line);
			_arrowPart->setArrowSize(_arrowSize);
			_notifyPart = new NotifyItem(_line);
            _notifyPart->setVisible(false);
		}

		/**
		 * @brief     line graphics item
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 13:23
		 */
		LineItem * _line{};

		/**
		 * @brief     from graphics item of line
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 13:16
		 */
		GraphicsItem * _fromItem{};

		/**
		 * @brief     to graphics item of line
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 13:16
		 */
		GraphicsItem * _toItem{};

		/**
		 * @brief     line graphics item 's normal pen
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 13:18
		 */
		QPen _penNormal{ QPen(QColor(25,56,199),2,Qt::SolidLine) };

        /**
         * @brief     line graphics item 's selected pen
         * @author    maguiwa(maguiwa520@163.com)
         * @date      2024-9-24 17:22
         */
        QPen _penSelected{ QPen(QColor(255,56,199),2,Qt::SolidLine) };

		/**
		 * @brief     line graphics item 's feature points. in item coordinate
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 13:20
		 */
		QList<QPoint> _featurePoints;

		/**
		 * @brief     bounding rect of graphics item
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 13:34
		 */
		QRect _boundingRect;

		/**
		 * @brief     the length of the first segment to turning point
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 17:21
		 */
		int _turningPointLength{ 40 };

		/**
		 * @brief     Tolerance distance of line item . used for select line item
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 14:28
		 */
		int _toleranceDistance{ 10 };

		/**
		 * @brief     arrow size of line item
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 15:05
		 */
		QSize _arrowSize{ QSize(18,12) };

		/**
		 * @brief     line type of line
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-2 15:17
		 */
		LineType _lineType;

		/**
		 * @brief     arrow part of line
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-3 16:20
		 */
		ArrowItem * _arrowPart{};

		/**
		 * @brief     line data state notify message
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-5 18:31
		 */
		QString _notify;

		/**
		 * @brief     notify part of line
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-5 17:03
		 */
		NotifyItem * _notifyPart{};

		/**
		 * @brief     update graphics item 's feature points
		 * @return    void 
		 * @author    maguiwa (maguiwa520@163.com)
		 * @date 	  2024-9-2 13:33
		 */
		void updateFeaturePoints()
		{
			/// calculate feature points and line type
			CalculateFeaturePoints calculator(_fromItem->portPart()->outputPortPos(), _toItem->portPart()->inputPortPos());
			calculator.setGraphicsItemTopHeight(_fromItem->boundingRect().height() / 2);
			calculator.setGraphicsItemBottomHeight(_fromItem->boundingRect().height() / 2 + _fromItem->namePart()->topBorderHeight() + _fromItem->namePart()->boundingRect().height());
			calculator.setGraphicsItemWidth(_fromItem->boundingRect().height() / 2);
			calculator.setArrowSize(_arrowSize);
			calculator.setTurningPointLength(_turningPointLength);
			_featurePoints = calculator.featurePoints();
			_lineType = calculator.getLineType();
		}

		/**
		 * @brief     update notify point
		 * @author    maguiwa(maguiwa520@163.com)
		 * @date      2024-9-5 18:24
		 */
		void updateNotifyPoint()
		{
			/// calculate notify point
			CalculateNotifyPoint calculator;
			QPoint pt = calculator.notifyPoint(_featurePoints);
			_notifyPart->setPos(pt + QPoint(-_notifyPart->boundingRect().width() / 2, -_notifyPart->boundingRect().height() / 2));
		}

		/**
		 * @brief     update line item 's bounding box
		 * @return    void 
		 * @author    maguiwa (maguiwa520@163.com)
		 * @date 	  2024-9-2 15:09
		 */
		void updateBoundingBox()
		{
			if (_featurePoints.isEmpty()) { return; }
			int xmin = _featurePoints.first().x(), xmax = xmin, ymin = _featurePoints.first().y(), ymax = ymin;

			for (QPoint pt : _featurePoints)
			{
				xmin = std::min(pt.x(), xmin);
				xmax = std::max(pt.x(), xmax);
				ymin = std::min(pt.y(), ymin);
				ymax = std::max(pt.y(), ymax);
			}

			_boundingRect = QRect(xmin, ymin, xmax - xmin, ymax - ymin);
		}

		void updateArrow()
		{
			_arrowPart->setPos(_featurePoints.last() + QPoint(-_arrowSize.width(), -_arrowSize.height() / 2));
		}
	};


	LineItem::LineItem(QGraphicsItem *parent /*= nullptr*/)
		: QGraphicsObject(parent)
		, _private(new LineItemPrivate(this))
	{
		setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
		setAcceptHoverEvents(true);
		setToolTip(tr("This a Line"));
	}

	LineItem::~LineItem()
	{
		if (_private)
		{
			delete _private;
			_private = nullptr;
		}
	}

	void LineItem::setFromGraphicsItem(GraphicsItem * item)
	{
		_private->_fromItem = item;
		updateLine();
	}

	Graphics::GraphicsItem * LineItem::fromGraphicsItem() const
	{
		return _private->_fromItem;
	}

	void LineItem::setToGraphicsItem(GraphicsItem * item)
	{
		_private->_toItem = item;
		updateLine();
	}

	Graphics::GraphicsItem * LineItem::toGraphicsItem() const
	{
		return _private->_toItem;
	}

	bool LineItem::selectTest(QPointF pos)
	{
		/// in item coordinate. do select test
		QPoint clickedPos = (pos - this->pos()).toPoint();
		int lineNum = _private->_featurePoints.size() - 1;

		CalculateIsPointNearLine2D calculator;
		for (int iLine = 0; iLine < lineNum; ++iLine)
		{
			QPoint pt0 = _private->_featurePoints[iLine];
			QPoint pt1 = _private->_featurePoints[iLine + 1];

			bool isNear = calculator.isNear(pt0, pt1, clickedPos, false, _private->_toleranceDistance);
			if (isNear)
			{
				setSelected(true);
				return true;
			}
		}
		setSelected(false);
		return false;
	}

    QPainterPath LineItem::shape() const
    {
        QPainterPath path;
        int lineNum = _private->_featurePoints.size() - 1;
        for (int iLine = 0; iLine < lineNum; ++iLine)
        {
            QPoint pt0 = _private->_featurePoints[iLine];
            QPoint pt1 = _private->_featurePoints[iLine + 1];

            if (pt0.x() == pt1.x())
            {
                int y = std::min(pt0.y(), pt1.y());
                path.addRect(pt0.x() - _private->_toleranceDistance, y, _private->_toleranceDistance * 2, fabs(pt1.y() - pt0.y()));
            }
            else if (pt0.y() == pt1.y())
            {
                int x = std::min(pt0.x(), pt1.x());
                path.addRect(x, pt0.y() - _private->_toleranceDistance, fabs(pt1.x() - pt0.x()), _private->_toleranceDistance * 2);
            }
        }
        return path;
    }

    QPen LineItem::pen() const
	{
		return _private->_penNormal;
	}

	void LineItem::setPen(QPen pen)
	{
		_private->_penNormal = pen;
		update();
	}

	QRectF LineItem::boundingRect() const
	{
        return _private->_boundingRect.adjusted(0, -_private->_toleranceDistance, 0, _private->_toleranceDistance);
	}

	void LineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget /* = nullptr */)
	{
		/// has no item to draw in line item. all visualization implemented in children objects
		Q_UNUSED(option);
		Q_UNUSED(widget);

		painter->save();
		painter->setRenderHint(QPainter::Antialiasing);
        GraphicsScene * sc = qobject_cast<GraphicsScene*>(scene());
        if (sc && isSelected())
        {
            if (sc->canvasMode() == Select_Mode)
            {
                /// need select part to change
            }
            else if (sc->canvasMode() == Line_Mode)
            {
                painter->setPen(_private->_penSelected);
                _private->_arrowPart->setPen(_private->_penSelected);
                _private->_arrowPart->setBrush(QBrush(_private->_penSelected.color()));
            }
        }
        else
        {
            painter->setPen(_private->_penNormal);
            _private->_arrowPart->setPen(_private->_penNormal);
            _private->_arrowPart->setBrush(QBrush(_private->_penNormal.color()));
        }
		
		QPoint orignPt = this->pos().toPoint();

		int ptNum = _private->_featurePoints.size();
		for (int iCnt = 1; iCnt < _private->_featurePoints.size(); ++iCnt)
		{
			/// line part from pt and to point . in scene coordinate
			QPoint fromPt = _private->_featurePoints[iCnt - 1] + orignPt;
			QPoint toPt = _private->_featurePoints[iCnt] + orignPt;

			painter->drawLine(fromPt, toPt);
		}

		painter->restore();
	}

	int LineItem::toleranceDistance() const
	{
		return _private->_toleranceDistance;
	}

	void LineItem::setToleranceDistance(int distance)
	{
		_private->_toleranceDistance = distance;
	}

	void LineItem::updateLine()
	{
		if (_private->_fromItem &&
			_private->_toItem)
		{
			_private->updateFeaturePoints();
			_private->updateNotifyPoint();
			prepareGeometryChange();
			_private->updateBoundingBox();
			_private->updateArrow();
		}
	}
}
